#include "innbbsconf.h"
#include "daemon.h"

extern int      errno;
static void 
reapchild(s)
  int             s;
{
  int             state;
  while (waitpid(-1, &state, WNOHANG | WUNTRACED) > 0) {
    /* printf("reaping child\n"); */
  }
}

void 
dokill(s)
  int             s;
{
  kill(0, SIGKILL);
}

static          INETDstart = 0;
int 
startfrominetd(flag)
{
  INETDstart = flag;
}


int 
standalonesetup(fd)
  int             fd;
{
  int             on = 1;
  struct linger   foobar;
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
    syslog(LOG_ERR, "setsockopt (SO_REUSEADDR): %m");
  foobar.l_onoff = 0;
  if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *)&foobar, sizeof(foobar)) < 0)
    syslog(LOG_ERR, "setsockopt (SO_LINGER): %m");
}

static char    *UNIX_SERVER_PATH;
static int      (*halt) ();

sethaltfunction(haltfunc)
  int             (*haltfunc) ();
{
  halt = haltfunc;
}

void 
docompletehalt(s)
  int             s;
{
  /*
   * printf("try to remove %s\n", UNIX_SERVER_PATH);
   * unlink(UNIX_SERVER_PATH);
   */
  exit(0);
  /* dokill(); */
}

void 
doremove(s)
  int             s;
{
  if (halt != NULL)
    (*halt) (s);
  else
    docompletehalt(s);
}


initunixserver(path, protocol)
  char           *path;
  char           *protocol;
{
  struct sockaddr_un s_un;
  /* unix endpoint address */
  struct protoent *pe;		/* protocol information entry */
  int             s;
  char           *ptr;

  bzero((char *)&s_un, sizeof(s_un));
  s_un.sun_family = AF_UNIX;
  strcpy(s_un.sun_path, path);
  if (protocol == NULL)
    protocol = "tcp";
  /* map protocol name to protocol number */
  pe = getprotobyname(protocol);
  if (pe == NULL) {
    fprintf(stderr, "%s: Unknown protocol.\n", protocol);
    return (-1);
  }
  /* Allocate a socket */
  s = socket(PF_UNIX, strcmp(protocol, "tcp") ? SOCK_DGRAM : SOCK_STREAM, 0);
  if (s < 0) {
    printf("protocol %d\n", pe->p_proto);
    perror("socket");
    return -1;
  }
  /* standalonesetup(s); */
  signal(SIGHUP, SIG_IGN);
  signal(SIGUSR1, SIG_IGN);
  signal(SIGCHLD, reapchild);
  UNIX_SERVER_PATH = path;
  signal(SIGINT, doremove);
  signal(SIGTERM, doremove);

  chdir("/");
  if (bind(s, (struct sockaddr *) & s_un, sizeof(struct sockaddr_un)) < 0) {
    perror("bind");
    perror(path);
    return -1;
  }
  listen(s, 10);
  return s;
}

initinetserver(service, protocol)
  char           *service;
  char           *protocol;
{
  struct servent *se;		/* service information entry */
  struct protoent *pe;		/* protocol information entry */
  struct sockaddr_in6 sin;	/* Internet endpoint address */
  int             port, s;
  int             randomport = 0;

  bzero((char *)&sin, sizeof(sin));
  sin.sin6_family = AF_INET6;

  if (!strcmp("0", service)) {
    randomport = 1;
    sin.sin6_addr = in6addr_any;
  }
  if (service == NULL)
    service = DEFAULTPORT;
  if (protocol == NULL)
    protocol = "tcp";
  /* map service name to port number */
  /* service ---> port */
  se = getservbyname(service, protocol);
  if (se == NULL) {
    port = htons((u_short) atoi(service));
    if (port == 0 && !randomport) {
      fprintf(stderr, "%s/%s: Unknown service.\n", service, protocol);
      return (-1);
    }
  } else
    port = se->s_port;
  sin.sin6_port = port;

  /* map protocol name to protocol number */
  pe = getprotobyname(protocol);
  if (pe == NULL) {
    fprintf(stderr, "%s: Unknown protocol.\n", protocol);
    return (-1);
  }
  /* Allocate a socket */
  s = socket(PF_INET6,
	   strcmp(protocol, "tcp") ? SOCK_DGRAM : SOCK_STREAM, pe->p_proto);
  if (s < 0) {
    perror("socket");
    return -1;
  }
  standalonesetup(s);
  signal(SIGHUP, SIG_IGN);
  signal(SIGUSR1, SIG_IGN);
  signal(SIGCHLD, reapchild);
  signal(SIGINT, dokill);
  signal(SIGTERM, dokill);

  chdir("/");
  if (bind(s, (struct sockaddr *) & sin, sizeof(struct sockaddr_in6)) < 0) {
    perror("bind");
    return -1;
  }
  listen(s, 10);
#ifdef DEBUG
  {
    int             length = sizeof(sin);
    getsockname(s, &sin, &length);
    printf("portnum alocalted %d\n", sin.sin6_port);
  }
#endif
  return s;
}

int 
open_unix_listen(path, protocol, initfunc)
  char           *path;
  char           *protocol;
  int             (*initfunc) ARG((int));
{
  int             s;
  s = initunixserver(path, protocol);
  if (s < 0) {
    return -1;
  }
  if (initfunc != NULL) {
    printf("in inetsingleserver before initfunc s %d\n", s);
    if ((*initfunc) (s) < 0) {
      perror("initfunc error");
      return -1;
    }
    printf("end inetsingleserver before initfunc \n");
  }
  return s;
}

int 
open_listen(service, protocol, initfunc)
  char           *service;
  char           *protocol;
  int             (*initfunc) ARG((int));
{
  int             s;
  if (!INETDstart)
    s = initinetserver(service, protocol);
  else
    s = 0;
  if (s < 0) {
    return -1;
  }
  if (initfunc != NULL) {
    printf("in inetsingleserver before initfunc s %d\n", s);
    if ((*initfunc) (s) < 0) {
      perror("initfunc error");
      return -1;
    }
    printf("end inetsingleserver before initfunc \n");
  }
  return s;
}

int 
inetsingleserver(service, protocol, serverfunc, initfunc)
  char           *service;
  char           *protocol;
  int             (*initfunc) ARG((int));
  int             (*serverfunc) ARG((int));
{
  int             s;
  if (!INETDstart)
    s = initinetserver(service, protocol);
  else
    s = 0;
  if (s < 0) {
    return -1;
  }
  if (initfunc != NULL) {
    printf("in inetsingleserver before initfunc s %d\n", s);
    if ((*initfunc) (s) < 0) {
      perror("initfunc error");
      return -1;
    }
    printf("end inetsingleserver before initfunc \n");
  } {
    int             ns = tryaccept(s);
    int             result = 0;
    if (ns < 0 && errno != EINTR) {
#ifdef DEBUGSERVER
      perror("accept");
#endif
    }
    close(s);
    if (serverfunc != NULL)
      result = (*serverfunc) (ns);
    close(ns);
    return (result);
  }
}


int 
tryaccept(s)
  int             s;
{
  int             ns, fromlen;
  struct sockaddr sockaddr;	/* Internet endpoint address */
  fromlen = sizeof(struct sockaddr_in6);

#ifdef DEBUGSERVER
  fputs("Listening again\n", stdout);
#endif
  do {
    ns = accept(s, &sockaddr, &fromlen);
    errno = 0;
  } while (ns < 0 && errno == EINTR);
  return ns;
}

int 
inetserver(service, protocol, serverfunc)
  char           *service;
  char           *protocol;
  int             (*serverfunc) ARG((int));
{
  int             port, s;

  if (!INETDstart)
    s = initinetserver(service, protocol);
  else
    s = 0;
  if (s < 0) {
    return -1;
  }
  for (;;) {
    int             ns = tryaccept(s);
    int             result = 0;
    int             pid;
    if (ns < 0 && errno != EINTR) {
#ifdef DEBUGSERVER
      perror("accept");
#endif
      continue;
    }
#ifdef DEBUGSERVER
    fputs("Accept OK\n", stdout);
#endif
    pid = fork();
    if (pid == 0) {
      close(s);
      if (serverfunc != NULL)
	result = (*serverfunc) (ns);
      close(ns);
      exit(result);
    } else if (pid < 0) {
      perror("fork");
      return -1;
    }
    close(ns);
  }
  return 0;
}

int 
inetclient(server, service, protocol)
  char           *server;
  char           *protocol;
  char           *service;
{
  struct addrinfo hints, *res;
  int             s;

  if (service == NULL)
    service = DEFAULTPORT;
  if (protocol == NULL)
    protocol = "tcp";
  if (server == NULL)
    server = DEFAULTSERVER;

  /* map server hostname to IP address, allowing for dotted decimal */
  bzero(&hints, sizeof(hints));
  hints.ai_flags = AI_CANONNAME;	// FQDN
  hints.ai_family = AF_UNSPEC;		// IPv6 first, then IPv4
  hints.ai_socktype = SOCK_STREAM;	// TCP

  if (getaddrinfo(server, service, &hints, &res) != 0) {
    fprintf(stderr, "%s: Unknown host.\n", server);
    return (-1);
  }
  /* Allocate a socket */
  s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
  if (s < 0) {
    perror("socket");
    return -1;
  }
  /* Connect the socket to the server */
  if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
    /* perror("connect"); */
    return -1;
  }
  freeaddrinfo(res);

  return s;
}

int 
unixclient(path, protocol)
  char           *path;
  char           *protocol;
{
  struct protoent *pe;		/* protocol information entry */
  struct sockaddr_un s_un;	/* unix endpoint address */
  int             s;

  bzero((char *)&s_un, sizeof(s_un));
  s_un.sun_family = AF_UNIX;

  if (path == NULL)
    path = DEFAULTPATH;
  if (protocol == NULL)
    protocol = "tcp";
  strcpy(s_un.sun_path, path);

  /* map protocol name to protocol number */
  pe = getprotobyname(protocol);
  if (pe == NULL) {
    fprintf(stderr, "%s: Unknown protocol.\n", protocol);
    return (-1);
  }
  /* Allocate a socket */
  s = socket(PF_UNIX, strcmp(protocol, "tcp") ? SOCK_DGRAM : SOCK_STREAM, 0);
  if (s < 0) {
    perror("socket");
    return -1;
  }
  /* Connect the socket to the server */
  if (connect(s, (struct sockaddr *) & s_un, sizeof(s_un)) < 0) {
    /* perror("connect"); */
    return -1;
  }
  return s;
}
